#include <winbuf/winbuf.h>

#define KMOD_SHIFT_L    0x01
#define KMOD_SHIFT_R    0x02
#define KMOD_SHIFT      (KMOD_SHIFT_L | KMOD_SHIFT_R)
#define KMOD_CTRL_L     0x04
#define KMOD_CTRL_R     0x08
#define KMOD_CTRL       (KMOD_CTRL_L | KMOD_CTRL_R)
#define KMOD_ALT_L      0x10
#define KMOD_ALT_R      0x20
#define KMOD_ALT        (KMOD_ALT_L | KMOD_ALT_R)
#define KMOD_PAD	     0x40
#define KMOD_NUM	     0x80
#define KMOD_CAPS	     0x100

NX_IMPORT NX_U32 * gBaseWinbuf;
NX_IMPORT NX_Size gBaseWinbufWidth;
NX_IMPORT NX_Size gBaseWinbufHeight;

NX_PRIVATE NX_Solt kbdSolt;
NX_PRIVATE NX_U32 kbdLeds;
NX_PRIVATE NX_U32 keyModify;
NX_PRIVATE NX_Solt mouseSolt;
NX_PRIVATE int mouseX;
NX_PRIVATE int mouseY;

NX_PRIVATE NX_Error WinbufFBInit(void)
{
    NX_Error err;
    NX_Printf("fb0!\n");

    /* open device */
    NX_Solt solt_fb = NX_DeviceOpen("fb0", 0);
    if (solt_fb < 0)
    {
        NX_Printf("winbuf: open video device failed!\n");
        return NX_ENORES;
    }

    NX_FramebufferInfo fbi;
    NX_Printf("fb1!\n");

    if (NX_DeviceControl(solt_fb, NX_FRAMEBUFFER_CMD_GETINFO, &fbi) != NX_EOK)
    {
        NX_Printf("winbuf: get video info failed!\n");
        err = NX_EPERM;
        goto close_fb_dev;
    }

    NX_Printf("winbuf: fbinfo: bpp:%d, scanline:%d, x:%d, y:%d, base:%p\n", fbi.bitsPerPixel, fbi.bytesPerScanLine, fbi.xResolution, fbi.yResolution, fbi.phyBasePtr);

    int bytes_per_pixel = fbi.bitsPerPixel / 8;
    NX_Size fb_len = bytes_per_pixel * fbi.yResolution * fbi.xResolution; 
    void *fb_buf = NX_DeviceMap(solt_fb, fb_len, NX_PROT_READ | NX_PROT_WRITE);
    if (fb_buf == NX_NULL) {
        NX_Printf("winbuf: map video buffer failed! %s\n", NX_ErrorToString(NX_ErrorGet()));
        err = NX_EPERM;
        goto close_fb_dev;
    }

    gBaseWinbuf = fb_buf;
    gBaseWinbufWidth = fbi.xResolution;
    gBaseWinbufHeight = fbi.yResolution;

    return NX_EOK;

close_fb_dev:
    NX_SoltClose(solt_fb);
    return err;
}

NX_PRIVATE void WinbufFBExit(void)
{
    gBaseWinbuf = NX_NULL;
    gBaseWinbufWidth = 0;
    gBaseWinbufHeight = 0;
}

NX_PRIVATE int keyboard_process_special_key(int code, int press)
{
    if (press) {
        /* home key */
        if (code == NX_KEY_HOME) {
            /* ctrl + Home ： => back to home */
            if (keyModify & (KMOD_CTRL)) {            
                /* top root prograne */
                TopWinbuf(GetRootWinbuf());
               return 1;
            }
        }
    }
    return 0;
}


NX_PRIVATE int keyboard_key_down(int code)
{
    if (code == NX_KEY_NUMLOCK) {
        if (keyModify & KMOD_NUM) {
            keyModify &= ~KMOD_NUM;
        } else {
            keyModify |= KMOD_NUM;    
        }
    }
    if (code == NX_KEY_CAPSLOCK) {
        if (keyModify & KMOD_CAPS) {
            keyModify &= ~KMOD_CAPS;
        } else {
            keyModify |= KMOD_CAPS;    
        }
    }
    
    switch (code) {
    case NX_KEY_LSHIFT:
        keyModify |= KMOD_SHIFT_L;
        break;
    case NX_KEY_RSHIFT:
        keyModify |= KMOD_SHIFT_R;
        break;
    case NX_KEY_LALT:
        keyModify |= KMOD_ALT_L;
        break;
    case NX_KEY_RALT:
        keyModify |= KMOD_ALT_R;
        break;
    case NX_KEY_LCTRL:
        keyModify |= KMOD_CTRL_L;
        break;
    case NX_KEY_RCTRL:
        keyModify |= KMOD_CTRL_R;
        break;
    default:
        break;
    }
    
    if (keyboard_process_special_key(code, 1))
        return -1;

    /* send msg to top winbuf */
    WinbufMsg m;
    SetWinbufMsgId(&m, WINBUF_MSG_KEY_DOWN);
    SetWinbufMsgData(&m, code, keyModify, 0);
    PutWinbufMsg(GetTopWinbuf(), &m);
    return 0;
}

NX_PRIVATE int keyboard_key_up(int code)
{
    switch (code) {
    case NX_KEY_LSHIFT:
        keyModify &= ~KMOD_SHIFT_L;
        break;
    case NX_KEY_RSHIFT:
        keyModify &= ~KMOD_SHIFT_R;
        break;
    case NX_KEY_LALT:
        keyModify &= ~KMOD_ALT_L;
        break;
    case NX_KEY_RALT:
        keyModify &= ~KMOD_ALT_R;
        break;
    case NX_KEY_LCTRL:
        keyModify &= ~KMOD_CTRL_L;
        break;
    case NX_KEY_RCTRL:
        keyModify &= ~KMOD_CTRL_R;
        break;
    default:
        break;
    }

    if (keyboard_process_special_key(code, 0))
        return -1;

    /* send msg to top winbuf */
    WinbufMsg m;
    SetWinbufMsgId(&m, WINBUF_MSG_KEY_UP);
    SetWinbufMsgData(&m, code, keyModify, 0);
    PutWinbufMsg(GetTopWinbuf(), &m);
    return 0;
}

NX_PRIVATE NX_Error WinbufKeyboardInit(void)
{
    kbdSolt = NX_DeviceOpen("keyboard0", 0);
    if (kbdSolt < 0)
    {
        return NX_EPERM;
    }
    keyModify = 0;
    kbdLeds = 0;
    NX_DeviceControl(kbdSolt, NX_INPUT_EVENT_CMD_GETLED, &kbdLeds);
    return NX_EOK;
}

NX_PRIVATE void WinbufKeyboardExit(void)
{
    NX_DeviceClose(kbdSolt);
}

NX_PRIVATE NX_Error WinbufMouseInit(void)
{
    mouseSolt = NX_DeviceOpen("mouse0", 0);
    if (mouseSolt < 0)
    {
        return NX_ENORES;
    }
    mouseX = 0;
    mouseY = 0;
    return NX_EOK;
}

void WinbufMouseExit(void)
{
    NX_DeviceClose(mouseSolt);
}

NX_PRIVATE void MouseMotion(void)
{
    if (mouseX < 0)
        mouseX = 0;
    if (mouseY < 0)
        mouseY = 0;
    if (mouseX > gBaseWinbufWidth - 1)
        mouseX = gBaseWinbufWidth - 1;
    if (mouseY > gBaseWinbufHeight - 1)
        mouseY = gBaseWinbufHeight - 1;

    WinbufMsg m;
    SetWinbufMsgId(&m, WINBUF_MSG_MOUSE_MOTION);
    SetWinbufMsgData(&m, mouseX, mouseY, 0);
    PutWinbufMsg(GetTopWinbuf(), &m);
}

NX_PRIVATE void MouseWheel(int wheel)
{
    int id = (wheel == 0) ? WINBUF_MSG_MOUSE_WHEEL_UP : WINBUF_MSG_MOUSE_WHEEL_DOWN;
    WinbufMsg m;
    SetWinbufMsgId(&m, id);
    SetWinbufMsgData(&m, mouseX, mouseY, 0);
    PutWinbufMsg(GetTopWinbuf(), &m);
}

NX_PRIVATE void MouseButtonDown(int button)
{
    int id = -1;
    switch (button) {
    case 0:
        id = WINBUF_MSG_MOUSE_LBTN_DOWN;
        break;
    case 1:
        id = WINBUF_MSG_MOUSE_MBTN_DOWN;
        break;
    case 2:
        id = WINBUF_MSG_MOUSE_RBTN_DOWN;
        break;
    default:
        break;
    }

    WinbufMsg m;
    SetWinbufMsgId(&m, id);
    SetWinbufMsgData(&m, mouseX, mouseY, 0);
    PutWinbufMsg(GetTopWinbuf(), &m);
}

NX_PRIVATE void MouseButtonUp(int button)
{
    int id = -1;
    switch (button) {
    case 0:
        id = WINBUF_MSG_MOUSE_LBTN_UP;
        break;
    case 1:
        id = WINBUF_MSG_MOUSE_MBTN_UP;
        break;
    case 2:
        id = WINBUF_MSG_MOUSE_RBTN_UP;
        break;
    default:
        break;
    }

    WinbufMsg m;
    SetWinbufMsgId(&m, id);
    SetWinbufMsgData(&m, mouseX, mouseY, 0);
    PutWinbufMsg(GetTopWinbuf(), &m);
}

NX_Error WinbufHalInit(void)
{
    NX_Error err;

    NX_Printf("winbuf: init 1!\n");
    if ((err = WinbufFBInit()) != NX_EOK)
    {
        NX_Printf("winbuf: init fb error!\n");
        return err;
    }

    NX_Printf("winbuf: init 2!\n");
    if ((err = WinbufKeyboardInit()) != NX_EOK)
    {
        NX_Printf("winbuf: init keyboard error!\n");
        WinbufFBExit();
        return err;
    }

    NX_Printf("winbuf: init 3!\n");
    if ((err = WinbufMouseInit()) != NX_EOK)
    {
        NX_Printf("winbuf: init mouse error!\n");
        WinbufKeyboardExit();
        WinbufFBExit();
        return err;
    }

    return NX_EOK;
}

NX_Error WinbufHalPoll(void)
{
    NX_PollState pState;
    NX_Solt solts[2];
    NX_InputEvent event;
    NX_Error err;

    solts[0] = kbdSolt;
    solts[1] = mouseSolt;
    pState.inmask = NX_POLL_READ;

    if ((err = NX_PollWait(solts, 2, -1, &pState)) == NX_EOK)
    {
        if (pState.solt == kbdSolt)
        {
            NX_MemZero(&event, sizeof(NX_InputEvent));
            
            if (NX_DeviceRead(kbdSolt, &event, 0, sizeof(NX_InputEvent)) <= 0)
            {
                return NX_ENORES;
            }

            /* handle event */
            if (event.type != NX_EV_KEY)
            {
                return NX_EPERM;
            }

            if (event.value)
            {
                keyboard_key_down(event.code);
            }
            else
            {
                keyboard_key_up(event.code);
            }
        }
        else if (pState.solt == mouseSolt)
        {
            NX_PRIVATE int x_rel = 0;
            NX_PRIVATE int y_rel = 0;
            NX_PRIVATE int z_rel = 0;
            NX_PRIVATE int flag_rel = 0;

need_next:
            NX_MemZero(&event, sizeof(event));
            if (NX_DeviceRead(mouseSolt, &event, 0, sizeof(event)) <= 0)
            {
                return NX_ENORES;
            }
            switch (event.type) {
            case NX_EV_REL:
                if ((event.code) == NX_REL_X) {
                    x_rel += event.value; 
                    flag_rel  = 1;
                    goto  need_next;
                } else if ((event.code) == NX_REL_Y) {
                    y_rel    += event.value; 
                    flag_rel  = 1;
                    goto  need_next;
                } else if ( (event.code) == NX_REL_WHEEL ) {
                    z_rel = (int )event.value;
                    MouseWheel(z_rel < 0 ? 0 : 1);
                    return  0;           
                } else {
                    return  0;
                }
                break;
            case NX_EV_KEY:
                if ((event.code) == NX_KEY_MOUSE_LEFT){
                    if (event.value > 0) {
                        MouseButtonDown(0);
                    } else {
                        MouseButtonUp(0);
                    }
                    return  0;
                } else if ((event.code) == NX_KEY_MOUSE_MIDDLE) {
                    if (event.value > 0) {
                        MouseButtonDown(1);
                    } else {
                        MouseButtonUp(1);
                    }
                    return  0;
                } else if ((event.code) == NX_KEY_MOUSE_RIGHT) {
                    if (event.value > 0) {
                        MouseButtonDown(2);
                    } else {
                        MouseButtonUp(2);
                    }
                    return  0;
                } else {
                    return  0;
                }
                break;
            case NX_EV_MISC:
                return  0;
            case NX_EV_SYNC:
                mouseX += x_rel;
                mouseY += y_rel;
                x_rel = 0;
                y_rel = 0;
                if (flag_rel == 1) {
                    MouseMotion();
                    flag_rel = 0;
                    return  0;
                }
                flag_rel = 0;
                break;
            default:
                break;
            }
        }
        else
        {
            return NX_EPERM;
        }
    }
    return err;
}

void WinbufHalExit(void)
{
    WinbufFBExit();
    WinbufKeyboardExit();
    WinbufMouseExit();
}
